import datetime

from django.conf import settings
from django.contrib.auth.models import User
from django.test import TestCase

from caching.middleware.cookies import StripCookiesMiddleware
from caching.middleware.utils import parse_http_date


class CachingTests(TestCase):

    urls = 'caching.tests.urls'

    def setUp(self):
        # TODO: Test this switcharoo of the cache backend
        self.cache_backend = settings.CACHE_BACKEND
        self.cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
        settings.CACHE_BACKEND = 'locmem://'
        settings.CACHE_MIDDLEWARE_SECONDS = 60 * 5

    def tearDown(self):
        settings.CACHE_BACKEND = self.cache_backend
        settings.CACHE_MIDDLEWARE_SECONDS = self.cache_middleware_seconds

    def test_authentication_status(self):

        User.objects.create_user(username='user1', email='', password='test')
        User.objects.create_user(username='user2', email='', password='test')

        # Fetch the page as an anonymous user.
        response = self.client.get('/test_authentication_status/')
        self.assertTrue(response.has_header('Vary'))
        self.assertFalse(response.has_header('X-From-Cache'))
        self.assertTrue(response.has_header('X-Vary-On-View'))
        self.assertEqual(response['X-Vary-On-View'], 'False;')
        self.assertContains(response, 'test_authentication_status: AnonymousUser')
        last_modified = response['Last-Modified']
        etag = response['ETag']

        # Test out the 304 Not Modified feature while we're here.
        response = self.client.get('/test_authentication_status/', HTTP_IF_MODIFIED_SINCE=last_modified)
        self.assertEqual(response.status_code, 304)
        response = self.client.get('/test_authentication_status/', HTTP_IF_NONE_MATCH=etag)
        self.assertEqual(response.status_code, 304)

        # Log in as a user, and try the same page. If we send a Last-Modified
        # header, it should not make a difference. This is because it has a
        # different vary_on_view value now that a user is authenticted.
        self.assertTrue(self.client.login(username='user1', password='test'))
        response = self.client.get('/test_authentication_status/', HTTP_IF_MODIFIED_SINCE=last_modified)
        self.assertEqual(response.status_code, 200)
        self.assertTrue(response.has_header('Vary'))
        self.assertFalse(response.has_header('X-From-Cache'))
        self.assertTrue(response.has_header('X-Vary-On-View'))
        self.assertEqual(response['X-Vary-On-View'], 'True;')
        self.assertContains(response, 'test_authentication_status: user1')

        # Try again and it will come from the cache.
        response = self.client.get('/test_authentication_status/')
        self.assertTrue(response.has_header('X-From-Cache'))
        response = self.client.get('/test_authentication_status/', HTTP_IF_NONE_MATCH=response['Etag'])
        self.assertEqual(response.status_code, 304)

        # Log out and it will get the cached version for anonymous users again.
        self.client.logout()
        response = self.client.get('/test_authentication_status/', )
        self.assertTrue(response.has_header('Vary'))
        self.assertTrue(response.has_header('X-From-Cache'))
        self.assertTrue(response.has_header('X-Vary-On-View'))
        self.assertEqual(response['X-Vary-On-View'], 'False;')
        self.assertContains(response, 'test_authentication_status: AnonymousUser')

        # Log in as user2 and we should see the cached version for user1.
        # This is intended for the test. Nobody in their right mind would put
        # user specific content in a view that is decorated with
        # @vary_on_authentication_status
        self.assertTrue(self.client.login(username='user2', password='test'))
        response = self.client.get('/test_authentication_status/')
        self.assertTrue(response.has_header('Vary'))
        self.assertTrue(response.has_header('X-From-Cache'))
        self.assertTrue(response.has_header('X-Vary-On-View'))
        self.assertEqual(response['X-Vary-On-View'], 'True;')
        self.assertContains(response, 'test_authentication_status: user1')

    def test_undecorated(self):

        # The first access will not be cached. The response should not have
        # any vary headers as it does not use any vary_on_view decorators,
        # and it does not rely on cookies (does not access the user).
        response = self.client.get('/test_undecorated/')
        self.assertEqual(response.status_code, 200)
        self.assertFalse(response.has_header('Vary'))
        self.assertFalse(response.has_header('X-From-Cache'))
        self.assertFalse(response.has_header('X-Vary-On-View'))
        last_modified = response['Last-Modified']
        etag = response['ETag']

        # This time, it should come from the cache.
        response = self.client.get('/test_undecorated/')
        self.assertEqual(response.status_code, 200)
        self.assertFalse(response.has_header('Vary'))
        self.assertTrue(response.has_header('X-From-Cache'))

        # Check if etags result in a 304 Not Modified response.
        response = self.client.get('/test_undecorated/', HTTP_IF_NONE_MATCH=etag)
        self.assertEqual(response.status_code, 304)

        # Log in as a user.
        User.objects.create_user(username='user1', email='', password='test')
        self.assertTrue(self.client.login(username='user1', password='test'))

        # 304 Not Modified should still work because the view doesn't vary.
        response = self.client.get('/test_undecorated/', HTTP_IF_NONE_MATCH=etag)
        self.assertEqual(response.status_code, 304)

        # Normal requests should come out of the cache.
        response = self.client.get('/test_undecorated/')
        self.assertEqual(response.status_code, 200)
        self.assertFalse(response.has_header('Vary'))
        self.assertTrue(response.has_header('X-From-Cache'))
        self.assertFalse(response.has_header('X-Vary-On-View'))

    def test_cache_control_decorator(self):

        # Django's decorator normally uses Django's middleware. It has been
        # swapped with the custom middleware, so check that it works.

        # Fetch the URL and it won't come from the cache. It should cache for
        # 60 days, as that is what is defined on the view.
        response = self.client.get('/test_cache_page/')
        self.assertFalse(response.has_header('X-From-Cache'))
        self.assertTrue(response.has_header('Expires'))

        # It should be cached for 60 days. Because it takes a split second to
        # run, it ends up being 59.999999 days (which rounds down to 59). Just
        # to be extra safe, even though it's impossible, allow for either
        # 59 or 60 days difference.
        expires = parse_http_date(response['Expires'])
        today = datetime.datetime.now()
        days_different = (expires - today).days
        self.assertTrue(days_different in (59, 60))

        # Fetch it again and it should come from the cache.
        response = self.client.get('/test_cache_page/')
        self.assertTrue(response.has_header('X-From-Cache'))
